﻿using System.Diagnostics;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using KinonekoSoftware.Foundation.GraphShape.Avalonia.Interfaces;

namespace GraphShape.Controls
{
    /// <summary>
    /// Graph element behavior.
    /// </summary>
    public class GraphElementBehaviour : AvaloniaObject
    {
        static GraphElementBehaviour()
        {
            IsHighlightedProperty.Changed.Subscribe(OnIsHighlightedPropertyChanged);
            HighlightInfoProperty.Changed.Subscribe(OnHighlightInfoPropertyChanged);
        }

        #region Highlight event

        /// <summary>
        /// Highlight event.
        /// </summary>
        [NotNull]
        public static readonly RoutedEvent HighlightEvent 
            = RoutedEvent.Register<GraphElementBehaviour,RoutedEventArgs>(
                                                                          "Highlight",
                                                                          RoutingStrategies.Bubble);
        /// <summary>
        /// Adds a new <see cref="HighlightEvent"/> handler.
        /// </summary>
        public static void AddHighlightHandler(AvaloniaObject d, EventHandler<RoutedEventArgs> handler)
        {
            if (d is Control uiElement)
            {
                uiElement.AddHandler(HighlightEvent, handler);
            }
        }

        /// <summary>
        /// Removes the given <paramref name="handler"/> from <see cref="HighlightEvent"/>.
        /// </summary>
        public static void RemoveHighlightHandler(AvaloniaObject d, EventHandler<RoutedEventArgs> handler)
        {
            if (d is Control uiElement)
            {
                uiElement.RemoveHandler(HighlightEvent, handler);
            }
        }

        #endregion

        #region Unhighlight event

        /// <summary>
        /// Unhighlight event.
        /// </summary>
        [NotNull]
        public static readonly RoutedEvent UnhighlightEvent
            = RoutedEvent.Register<GraphElementBehaviour,RoutedEventArgs>(
                                                                          "Unhighlight",
                                                                          RoutingStrategies.Bubble);

        /// <summary>
        /// Adds a new <see cref="UnhighlightEvent"/> handler.
        /// </summary>
        public static void AddUnhighlightHandler(AvaloniaObject d, EventHandler<RoutedEventArgs> handler)
        {
            if (d is Control uiElement)
            {
                uiElement.AddHandler(UnhighlightEvent, handler);
            }
        }

        /// <summary>
        /// Removes the given <paramref name="handler"/> from <see cref="UnhighlightEvent"/>.
        /// </summary>
        public static void RemoveUnhighlightHandler(AvaloniaObject d, EventHandler<RoutedEventArgs> handler)
        {
            if (d is Control uiElement)
            {
                uiElement.RemoveHandler(UnhighlightEvent, handler);
            }
        }

        #endregion

        #region HighlightTriggered event

        /// <summary>
        /// Highlight trigger event.
        /// </summary>
        [NotNull]
        internal static readonly RoutedEvent HighlightTriggeredEvent
            = RoutedEvent.Register<GraphElementBehaviour, HighlightTriggeredEventArgs>(
                                                                                       "HighlightTriggered",
                                                                                       RoutingStrategies.Bubble);

        /// <summary>
        /// Adds a new <see cref="HighlightTriggeredEvent"/> handler.
        /// </summary>
        public static void AddHighlightTriggeredHandler(AvaloniaObject d, EventHandler<HighlightTriggeredEventArgs> handler)
        {
            if (d is Control uiElement)
            {
                uiElement.AddHandler(HighlightTriggeredEvent, handler);
            }
        }

        /// <summary>
        /// Removes the given <paramref name="handler"/> from <see cref="HighlightTriggeredEvent"/>.
        /// </summary>
        public static void RemoveHighlightTriggeredHandler(AvaloniaObject d, EventHandler<HighlightTriggeredEventArgs> handler)
        {
            if (d is Control uiElement)
            {
                uiElement.RemoveHandler(HighlightTriggeredEvent, handler);
            }
        }

        #endregion

        #region HighlightInfoChanged event

        /// <summary>
        /// Highlight information changed event.
        /// </summary>
        [NotNull]
        public static readonly RoutedEvent HighlightInfoChangedEvent
            = RoutedEvent.Register<GraphElementBehaviour, HighlightInfoChangedEventArgs>("HighlightInfoChanged", RoutingStrategies.Bubble);

        /// <summary>
        /// Adds a new <see cref="HighlightInfoChangedEvent"/> handler.
        /// </summary>
        public static void AddHighlightInfoChangedHandler(AvaloniaObject d, EventHandler<HighlightInfoChangedEventArgs> handler)
        {
            if (d is Control uiElement)
            {
                uiElement.AddHandler(HighlightInfoChangedEvent, handler);
            }
        }

        /// <summary>
        /// Removes the given <paramref name="handler"/> from <see cref="HighlightInfoChangedEvent"/>.
        /// </summary>
        public static void RemoveHighlightInfoChangedHandler(AvaloniaObject d, EventHandler<HighlightInfoChangedEventArgs> handler)
        {
            if (d is Control uiElement)
            {
                uiElement.RemoveHandler(HighlightInfoChangedEvent, handler);
            }
        }

        #endregion

        #region HighlightTrigger

        /// <summary>
        /// Highlight trigger dependency property.
        /// </summary>
        [NotNull]
        public static readonly AttachedProperty<bool> HighlightTriggerProperty
            = AvaloniaProperty.RegisterAttached<GraphElementBehaviour, Interactive, bool>(
                                                                                          "HighlightTrigger",
                                                                                          false,
                                                                                          false,
                                                                                          BindingMode.OneWay,
                                                                                          null,
                                                                                          CoerceHighlightTrigger);

        /// <summary>
        /// Gets the <see cref="HighlightTriggerProperty"/> value.
        /// </summary>
        public static bool GetHighlightTrigger(AvaloniaObject obj)
        {
            return obj.GetValue(HighlightTriggerProperty);
        }

        /// <summary>
        /// Sets the <see cref="HighlightTriggerProperty"/> value.
        /// </summary>
        public static void SetHighlightTrigger(AvaloniaObject obj, bool value)
        {
            obj.SetValue(HighlightTriggerProperty, value);
        }

        private static bool CoerceHighlightTrigger(AvaloniaObject d, bool baseValue)
        {
            var control = d as Control;
            if (control is null)
                return baseValue;

            if (baseValue == GetHighlightTrigger(d))
                return baseValue;

            var args = new HighlightTriggeredEventArgs(HighlightTriggeredEvent, d, baseValue);
            try
            {
                control.RaiseEvent(args);
            }
            catch(Exception ex)
            {
                Debug.WriteLine($"Exception during CoerceHighlightTrigger - likely the graph is still animating: {ex}");
            }

            return args.Cancel ? GetHighlightTrigger(d) : baseValue;
        }

        #endregion

        #region IsHighlighted

        /// <summary>
        /// Is highlighted property.
        /// </summary>
        [NotNull]
        private static readonly AttachedProperty<bool> IsHighlightedProperty
            = AvaloniaProperty.RegisterAttached<GraphElementBehaviour, Interactive, bool>("IsHighlighted");

        /// <summary>
        /// Gets the <see cref="IsHighlightedProperty"/> value.
        /// </summary>
        public static bool GetIsHighlighted(AvaloniaObject obj)
        {
            return obj.GetValue(IsHighlightedProperty);
        }

        /// <summary>
        /// Sets the <see cref="IsHighlightedProperty"/> value.
        /// </summary>
        internal static void SetIsHighlighted(AvaloniaObject obj, bool value)
        {
            obj.SetValue(IsHighlightedProperty, value);

            (obj as IHighlightStateSource)?.CheckHighlightState();
        }

        // When the IsHighlighted Property changes we should raise the 
        // Highlight and Unhighlight RoutedEvents.
        private static void OnIsHighlightedPropertyChanged(AvaloniaPropertyChangedEventArgs<bool> args)
        {
            var control = args.Sender as Control;
            if (control is null)
            {
                return;
            }

            if (args.GetNewValue<bool>())
            {
                control.RaiseEvent(new RoutedEventArgs(HighlightEvent, control));
            }
            else
            {
                control.RaiseEvent(new RoutedEventArgs(UnhighlightEvent, control));
            }
        }

        #endregion

        #region HighlightInfo

        /// <summary>
        /// Highlight information property key.
        /// </summary>
        [NotNull]
        private static readonly AttachedProperty<object> HighlightInfoProperty
            = AvaloniaProperty.RegisterAttached<GraphElementBehaviour, Interactive, object>("HighlightInfo");


        /// <summary>
        /// Gets the <see cref="HighlightInfoProperty"/> value.
        /// </summary>
        public static object GetHighlightInfo(AvaloniaObject obj)
        {
            return obj.GetValue(HighlightInfoProperty);
        }

        /// <summary>
        /// Sets the <see cref="HighlightInfoProperty"/> value.
        /// </summary>
        internal static void SetHighlightInfo(AvaloniaObject obj, object value)
        {
            obj.SetValue(HighlightInfoProperty, value);
        }

        private static void OnHighlightInfoPropertyChanged(AvaloniaPropertyChangedEventArgs<object> args)
        {
            var control = args.Sender as Control;
            control?.RaiseEvent(new HighlightInfoChangedEventArgs(HighlightInfoChangedEvent, control, args.OldValue, args.NewValue));
        }

        #endregion

        #region IsSemiHighlighted

        /// <summary>
        /// Is semi highlighted dependency property.
        /// </summary>
        [NotNull]
        private static readonly AttachedProperty<bool> IsSemiHighlightedProperty
            = AvaloniaProperty.RegisterAttached<GraphElementBehaviour, Interactive, bool>("IsSemiHighlighted");

        /// <summary>
        /// Gets the <see cref="IsSemiHighlightedProperty"/> value.
        /// </summary>
        public static bool GetIsSemiHighlighted(AvaloniaObject obj)
        {
            return obj.GetValue(IsSemiHighlightedProperty);
        }

        /// <summary>
        /// Sets the <see cref="IsSemiHighlightedProperty"/> value.
        /// </summary>
        internal static void SetIsSemiHighlighted(AvaloniaObject obj, bool value)
        {
            obj.SetValue(IsSemiHighlightedProperty, value);
            (obj as IHighlightStateSource)?.CheckHighlightState();
            
        }

        #endregion

        #region SemiHighlightInfo

        /// <summary>
        /// Semi highlight information property key.
        /// </summary>
        [NotNull]
        private static readonly AttachedProperty<object> SemiHighlightInfoProperty
            = AvaloniaProperty.RegisterAttached<GraphElementBehaviour, Interactive, object>("SemiHighlightInfo");


        /// <summary>
        /// Gets the <see cref="SemiHighlightInfoProperty"/> value.
        /// </summary>
        public static object GetSemiHighlightInfo(AvaloniaObject obj)
        {
            return obj.GetValue(SemiHighlightInfoProperty);
        }

        /// <summary>
        /// Sets the <see cref="SemiHighlightInfoProperty"/> value.
        /// </summary>
        internal static void SetSemiHighlightInfo(AvaloniaObject obj, object value)
        {
            obj.SetValue(SemiHighlightInfoProperty, value);
        }

        #endregion

        #region LayoutInfo

        /// <summary>
        /// Layout information dependency property.
        /// </summary>
        [NotNull]
        public static readonly AttachedProperty<object> LayoutInfoProperty = AvaloniaProperty.RegisterAttached<GraphElementBehaviour, Interactive, object>("LayoutInfo");

        /// <summary>
        /// Gets the <see cref="LayoutInfoProperty"/> value.
        /// </summary>
        public static object GetLayoutInfo(Interactive obj)
        {
            return obj.GetValue(LayoutInfoProperty);
        }

        /// <summary>
        /// Sets the <see cref="LayoutInfoProperty"/> value.
        /// </summary>
        public static void SetLayoutInfo(Interactive obj, object value)
        {
            obj.SetValue(LayoutInfoProperty, value);
        }

        #endregion
    }
}